#include "../core/core.h"
#include "thread_pool.h"
#include "singleton.h"

#include <vector>
#include <array>
#include <string>
#include <fstream>

namespace csugl::pt
{
    using FloatingPointMicroSeconds = std::chrono::duration<double, std::micro>;
	
	template <typename T>
	struct LQueue
	{
	private:
		struct LNode
		{
			LNode() {}
			T *val = nullptr;
			LNode *next = nullptr, *pre = nullptr;
		};
		LNode *m_Rear, *m_Front;
		std::atomic_size_t m_Size;

	public:
		LQueue() : m_Rear(new LNode()), m_Front(m_Rear), m_Size(0) {}
		inline bool is_empty() const { return m_Rear == m_Front; }
		inline size_t size() const { return m_Size.load(); }
		void push_front(T *val)
		{
			m_Front->val = val;
			LNode *newN = new LNode();
			m_Front->next = newN;
			newN->pre = m_Front;
			m_Front = newN;

			m_Size++;
		}
		void pop_back()
		{
			if (is_empty())
				throw std::bad_exception();
			auto _rear = m_Rear;
			m_Rear = m_Rear->next;
			m_Rear->pre = nullptr;
			delete _rear;

			m_Size--;
		}
		inline T *rear() { return m_Rear->next->pre->val; }
		inline T *front() { return m_Front->pre->val; }
	};

	struct ProfileResult
	{
		std::string name;

		FloatingPointMicroSeconds start;
		std::chrono::microseconds elapsedTime;
		std::thread::id threadID;
	};

	struct InstrumentationSession
	{
		std::string name;
	};

	class InstrumentCache
	{
	public:
		InstrumentCache(const InstrumentCache&) = delete;

		InstrumentCache(InstrumentCache&&) = delete;

		bool Begin(const std::string filePath);

		void End();
		
		/// <summary>
		/// Push result into cache
		/// </summary>
		/// <param name="result"></param>
		void operator<<(const ProfileResult& result);

		void WriteFileHeader();

		void WriteFileFooter();

		static InstrumentCache& GetInstance()
		{
			static InstrumentCache instance;
			return instance;
		}

	private:
		InstrumentCache()
		{
		}
		
		void WriteFileContent();
		
		static const size_t maxSize = 4096;

		size_t curResult = 0;

		LQueue<std::vector<ProfileResult>> m_Q;
		std::thread m_Save_thread;
		bool is_writting_content = false;
		
		std::ofstream m_OutputStream;

	};

	class Instrumentor
	{
	public:
		Instrumentor(const Instrumentor&) = delete;

		Instrumentor(Instrumentor&&) = delete;

		/// <summary>
		/// Begin session
		/// </summary>
		/// <param name="name">Session name, from line or func</param>
		/// <param name="filePath"></param>
		void BeginSession(const std::string& name, const std::string& filePath = "results.json");

		/// <summary>
		/// End session
		/// </summary>
		void EndSession();

		/// <summary>
		/// Write profile
		/// </summary>
		/// <param name="result"></param>
		void WriteProfile(ProfileResult&& result);

		static Instrumentor& GetInstance()
		{
			static Instrumentor instance;
			return instance;
		}

	private:
		Instrumentor()
			:m_CurrentSession(nullptr)
		{}

		~Instrumentor()
		{
			EndSession();
		}

		void WriteHeader()
		{
			InstrumentCache::GetInstance().WriteFileHeader();
            // singleton<InstrumentCache>::getInstance()->WriteFileHeader();
		}

		void WriteFooter()
		{
			InstrumentCache::GetInstance().WriteFileFooter();
            // singleton<InstrumentCache>::getInstance()->WriteFileFooter();
		}

		void InternalEndSession();

	private:
		std::mutex m_Mutex;

		InstrumentationSession* m_CurrentSession;

		//std::ofstream m_OutputStream;
	};

	class InstrumentationTimer
	{
	public:
		InstrumentationTimer(const char* name)
			:m_Name(name), isStoped(false)
		{
			m_StartTimePoint = std::chrono::high_resolution_clock::now();
		}

		~InstrumentationTimer()
		{
			if (!isStoped)
				Stop();
		}

	private:
		void Stop()
		{
			auto endTimePoint = std::chrono::high_resolution_clock::now();
			auto highResStart = FloatingPointMicroSeconds{ m_StartTimePoint.time_since_epoch() };
			auto elapsedTime = std::chrono::time_point_cast<std::chrono::microseconds>(endTimePoint).time_since_epoch()
				- std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimePoint).time_since_epoch();

			Instrumentor::GetInstance().WriteProfile({ m_Name, highResStart ,elapsedTime,std::this_thread::get_id() });
			// singleton<Instrumentor>::getInstance()->WriteProfile({ m_Name, highResStart ,elapsedTime,std::this_thread::get_id() });

			isStoped = true;
		}

	private:
		const char* m_Name;

		std::chrono::time_point<std::chrono::steady_clock> m_StartTimePoint;

		bool isStoped;

	};

	namespace InstrumentorUtils {

		template <size_t N>
		struct ChangeResult
		{
			char Data[N];
		};

		template <size_t N, size_t K>
		constexpr auto CleanupOutputString(const char(&expr)[N], const char(&remove)[K])
		{
			ChangeResult<N> result = {};

			size_t srcIndex = 0;
			size_t dstIndex = 0;
			while (srcIndex < N)
			{
				size_t matchIndex = 0;
				while (matchIndex < K - 1 && srcIndex + matchIndex < N - 1 && expr[srcIndex + matchIndex] == remove[matchIndex])
					matchIndex++;
				
				if (matchIndex == K - 1)
					srcIndex += matchIndex;
				
				result.Data[dstIndex++] = expr[srcIndex] == '"' ? '\'' : expr[srcIndex];
				srcIndex++;
			}
			return result;
		}

	}
} // namespace csugl::pt

#ifndef ST_PROFILE 
    #define ST_PROFILE 1
#endif

#if ST_PROFILE
	#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__)
		#define ST_FUNC_SIG __PRETTY_FUNCTION__
	#elif defined(__DMC__) && (__DMC__ >= 0x810)
		#define ST_FUNC_SIG __PRETTY_FUNCTION__
	#elif (defined(__FUNCSIG__) || (_MSC_VER))
		#define ST_FUNC_SIG __FUNCSIG__
	#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))
		#define ST_FUNC_SIG __FUNCTION__
	#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)
		#define ST_FUNC_SIG __FUNC__
	#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)
		#define ST_FUNC_SIG __func__
	#elif defined(__cplusplus) && (__cplusplus >= 201103)
		#define ST_FUNC_SIG __func__
	#else
		#define ST_FUNC_SIG "ST_FUNC_SIG unknown!"
	#endif

	#define ST_PROFILE_BEGIN_SESSION(name, filepath) csugl::pt::Instrumentor::GetInstance().BeginSession(name, filepath)
	#define ST_PROFILE_END_SESSION() csugl::pt::Instrumentor::GetInstance().EndSession()
	#define ST_PROFILE_SCOPE_LINE2(name, line) constexpr auto fixedName##line = csugl::pt::InstrumentorUtils::CleanupOutputString(name, "__cdecl ");\
											   csugl::pt::InstrumentationTimer timer##line(fixedName##line.Data)
	#define ST_PROFILE_SCOPE_LINE(name, line) ST_PROFILE_SCOPE_LINE2(name, line)
	#define ST_PROFILE_SCOPE(name) ST_PROFILE_SCOPE_LINE(name, __LINE__)
	#define ST_PROFILE_FUNCTION() ST_PROFILE_SCOPE(ST_FUNC_SIG)
#else
	#define ST_PROFILE_BEGIN_SESSION(name, filepath)
	#define ST_PROFILE_END_SESSION()
	#define ST_PROFILE_SCOPE(name)
	#define ST_PROFILE_FUNCTION()
#endif // ST_PROFILE
